import inspect, yaml, os, types

from datastore import DurusDB
from pprint import pprint

from core.base import ComponentStem, ComponentMeta, ComponentBase, NamedComponent, \
                      ServerComponent, Plugin, ComponentRenamed, NameAboutToChange
from core import server
from core import util

import typestore

class PluginManager(object):
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = object.__new__(cls)
            cls._instance.plugins = []
        return cls._instance
    
    
    def init(self, setting_file, plugin_path, scene):
        self.setting_file = setting_file
        self.plugin_path = plugin_path
        self.scene = scene
        
        
    def new_component_plugin(self, basetype, name=None):
        typename = basetype.__name__
        nodename = "MyNode" if name is None else name
        template_code = """
'''
Plugin documentation
'''

class %(nodename)s(ni.%(typename)s):
    ''' Component documentation '''
    # Define attributes here.
    
""" % {'nodename':nodename, 'typename':typename}
        plugin = Plugin(None)
        self.plugins.append(plugin)
        plugin.set_code(template_code)
        self.load_plugin(plugin)
        self.commit_plugin(plugin)
        
    def load_plugin(self, plugin):
        print 'loading plugin', plugin, plugin.filepath
        api_name = "ni"
        import api
        api = api.API()
        content = {}
        #for name, value in locals().items():
        #    content[name] = value
        local_content = {}
        content[api_name] = api
        
        if plugin.filepath is not None:
            execfile(plugin.filepath, content)#, local_content)
        else:
            exec plugin.code() in content#, local_content
        plugin.doc = content.get('__doc__')
        plugin.name = content.get('name', plugin.default_name())
        plugin.components = []
        
        for name, value in content.items():
            if not isinstance(value, (types.ClassType, type)): 
                continue
            
            if issubclass(value, ComponentBase):
                # We need to get the loaded class, since the class in the dict might not be the one loaded.
                component = self.scene.type_class(name)
                print 'SETTING PLUGIN', plugin, 'ON COMPONENT', component
                if component is None:
                    continue
                component.plugin = plugin
                plugin.components.append(name)
    
    def commit_plugin(self, plugin):
        server_id = "" if plugin.server_id is None else plugin.server_id
        revision_id = "" if plugin.revision_id is None else plugin.revision_id
        plugin.server_id, plugin.revision_id = \
                server.add_revision(server_id, revision_id, plugin.name, plugin.code(), plugin.components)
        self.save()
        
    def fetch_server_plugins(self):
        plugin_dict = server.all_plugins()
        if plugin_dict is None:
            return
        
        for plugin_key, plugin_data in plugin_dict.items():
            plugin = self.get_by_id(plugin_key)
            
            if not plugin is None:
                # Plugin already exists
                continue
            
            plugin = Plugin(None)
            plugin.cached_code = plugin_data['latest_code']
            plugin.revision_id = plugin_data['latest_rev_id']
            objects = plugin_data.get('latest_objects',[])
            for obj in map(str, objects):
                assert isinstance(obj, str)
                stem = ComponentMeta(obj, (ServerComponent,), {})
                
                stem.plugin = plugin
            plugin.server_id = plugin_key
            self.plugins.append(plugin)
    
    def fetch_local_plugins(self):
        for root, dirs, files in os.walk(self.plugin_path):
            for file in files:
                if file.startswith("_"):
                    continue
                filepath = root + os.sep + file
                if util.is_pyfile(filepath):
                    plugin = Plugin(filepath)
                    self.plugins.append(plugin)
    
    def fetch_revisions(self):
        pass
        
    def get_by_name(self, name):
        for plugin in self.plugins:
            if name == plugin.name:
                return plugin
        
    def get_by_id(self, id):
        for plugin in self.plugins:
            if id == plugin.server_id:
                return plugin
        
    def load_plugins(self):
        self.plugins = []
        self.fetch_local_plugins()
        for plugin in self.plugins: 
            self.load_plugin(plugin)
        self.fetch_server_plugins()
        
    
    def save(self):
        server_map = {}
        data_dict = {'server map':server_map}
        for plugin in self.plugins:
            if plugin.server_id is None or plugin.revision_id is None:
                continue
            server_map[plugin.server_id] = plugin.revision_id
            
        with open(self.setting_file, 'w') as f:
            content = yaml.dump(data_dict)
            f.write(content)
    
    def load(self):
        self.load_plugins()
        data_dict = {}
        with open(self.setting_file) as f:
            data_dict = yaml.load(f.read())
        if not data_dict.has_key('server map'):
            return
        
        for server_id, rev_id in data_dict.get('server map').items():
            plugin = self.get_by_id(server_id)
            if plugin is None:
                continue
            plugin.server_id = server_id
            plugin.revision_id = rev_id

'''

Scene Storage Structure:

Type Name (String):
    "Class" (String): Class Object (Class)
    "Instances" (String):
        Instance Name (String): List of Instances (List)

'''


class Scene(object):
    ''' Management of instances and types.
        Stores classes and its related instances under a uniq name.
    '''
    
    def __init__(self):
        self._root = None    
        self.cache = {}
        self.stem_components = {}
        self.server_components = []
        self.classes = {}
        self.db = DurusDB()
        NameAboutToChange.connect(self.rename)
    
    def get_root(self):
        if self._root is None:
            return self.cache
        return self._root
    
    root = property(get_root)
    
    def connect(self):
        print 'CONNECT'
        #self.db.connect()
        #self._root = self.db.root
        #self.move_cache_to_db()
    
    def move_cache_to_db(self):
        for name, value in self.cache.items():
            if not self._root.has_key(name):
                self._root[name] = {}
    
    def clear(self):
        for name in self.root.keys():
            self.root[name] = {}
        self.save()
        
    def reload(self):
        self.db.reload()
    
    def print_db(self, root=None, spaces=0):
        pprint(self.root)
        
    def load(self):
        pass
        
    def save(self):
        self.db.commit()
    
    def has_type(self, name):
        return self.classes.has_key(name)
        
    def get(self, type_name, inst_name):
        return self.db.get(type_name, inst_name)
        
    def instance_names(self, type_name):
        return self.db.instance_names(type_name)
        
    def subclasses(self, cls):
        if isinstance(cls, str):
            cls = self.classes.get(cls)
        
        for klass in self.classes.values():
            if issubclass(klass, cls):
                yield klass
                
    
    def instances(self, type_name):
        if inspect.isclass(type_name):
            type_name = type_name.__name__
        instances = []
        try:
            for cls in self.subclasses(type_name):
                instances.extend(list(self.db.instances(cls)))
        except AttributeError, e:
            print str(e)
            return []
        return instances
    #def name_of(self, instance):
    #    inst_type = self.instance_type(instance)
    #    for name, inst in self.root[inst_type].iteritems():
    #        if inst is instance:
    #            return name
        
    def name_exists(self, inst_type, name):
        return name in self.instance_names(inst_type)
        
    def uniq_name(self, instance, name):
        ''' '''
        inst_type = self.instance_type(instance)
        type_name = inst_type.__name__
        uniq_name = name
        counter = 0
        while 1:
            if not self.name_exists(type_name, uniq_name):
                return uniq_name
            else:
                uniq_name = name+str(counter)
            counter += 1
        
    def rename(self, instance, old_name, new_name):
        print 'RENAMEING', new_name
        if new_name is None:
            new_name = self.default_name(instance)
        uniq_name = self.uniq_name(instance, new_name)
        instance._name = uniq_name
        self.db._rename(instance, old_name, uniq_name)
        ComponentRenamed(instance, old_name, uniq_name)
        
    def instance_type(self, instance):
        return instance.__class__
        
    def default_name(self, instance):
        return str(instance.__class__.__name__)
    
    def types(self):
        for value in self.classes.values():
            yield value
        
        # Yes, I Know..
        # @todo: fix this hack somehow.
        yield ServerComponent
    
    def note_change(self):
        if self._root is None: return
        self.root._p_note_change()
    
    def instances_by_typename(self, name):
        return self.root[name]["instances"].values()
    
    def add_instance(self, instance, override=False):
        ''' '''
        inst_type = self.instance_type(instance)
        type_name = inst_type.__name__
        print 'adding instance', self.has_type(type_name), instance, inst_type, issubclass(inst_type, ComponentStem)
        #if not self.has_type(type_name):
        #    if force:
        #        self.add_type(inst_type)
        #    else:
        #        raise ValueError("Instance type not registered: " + str(inst_type))
        
        inst_name = instance.name
        if inst_name is None:
            inst_name = self.default_name(instance)
        if not override:
            inst_name = self.uniq_name(instance, inst_name)
        instance._name = inst_name
        #self.root[type_name]["instances"][inst_name] = instance
        #print 'add instance', instance, inst_type, name, inst_type.__bases__
        
        self.db.add_instance(instance)
        self.note_change()
        self.save()

    def add_server_type(self, inst):
        #if self.root is None: 
        #    print 'cannot add server type, root is not defined'
        #    return
        
        self.server_components.append(inst)#[inst.name] = inst

    def add_type(self, cls, force=True):
        ''' '''
        #print 'add type', cls
        if self.root is None: return
        name = cls.__name__
        print 'adding type', name, cls, cls.__bases__
        if name in self.stem_components.keys() and not issubclass(cls, ComponentStem):
            print 'stem exists'
            stem = self.stem_components.get(name)
            
            stem.transform_into(cls, self.instances(name))
            cls = stem
            del self.stem_components[name]
            return
            
        if issubclass(cls, ComponentStem):
            self.stem_components[name] = cls
            # Dont return here, we need to but the stem in root as well
            #return
            
        if self.has_type(name):
            
            existing_cls = self.type_class(name)
            print 'REPLACING', existing_cls, 'WITH', cls
            existing_cls.replace_with(cls)
            #print 'TYPE ALREADY EXISTS', cls, existing
            #if force:
            
            #self.replace_instances_type(cls, existing)
            return
        
        #if issubclass(cls, ComponentBase):
        #    print 'adding class', list(cls.attrs())
        cls.__module__ = 'typestore'
        setattr(typestore, name, cls)
        self.classes[name] = cls
        
        #self.root[name] = {}
        #self.root[name]["instances"] = {}
        #self.root[name]["class"] = cls
        #self.note_change()
        #self.db.connection.commit()
        
    def replace_instances_type(self, cls, instances):
        ''' '''
        for inst in instances:
            print "replace", inst, "class:", inst.__class__, "with", cls 
            inst.__class__ = cls
             
    def type_class(self, type_name):
        try:
            return self.classes.get(type_name)
        except AttributeError:
            return None
        
    def get_type(self, name):
        cls = self.type_class(name)
        if cls is not None:
            return cls
        
        stem = ComponentMeta(name, (ComponentBase,), {})
        #stem = ComponentMeta(name, (ComponentStem,), {})
        #self.stem_components[name] = stem
        return stem
    
    
    